/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.wsonrpc.util;

import net.apexes.wsonrpc.core.exception.RemoteInternalException;
import net.apexes.wsonrpc.core.exception.RemoteInvalidParamsException;
import net.apexes.wsonrpc.core.exception.RemoteInvalidRequestException;
import net.apexes.wsonrpc.core.exception.RemoteMethodNotFoundException;
import net.apexes.wsonrpc.core.exception.RemoteParseException;
import net.apexes.wsonrpc.core.exception.RemoteServerException;
import net.apexes.wsonrpc.json.JsonRpcError;
import net.apexes.wsonrpc.json.JsonRpcErrorCause;
import net.apexes.wsonrpc.json.JsonRpcErrorCauseStackTrace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 */
public class JsonRpcErrors {
    private JsonRpcErrors() {}

    private static final Logger LOG = LoggerFactory.getLogger(JsonRpcErrors.class);

    public static JsonRpcError parseError() {
        return parseError((Throwable)null);
    }

    public static JsonRpcError parseError(String message) {
        return parseError(message, null);
    }

    public static JsonRpcError parseError(Throwable t) {
        return parseError("Parse error", t);
    }

    public static JsonRpcError parseError(String message, Throwable t) {
        return new JsonRpcError(RemoteParseException.CODE, message, JsonRpcErrors.toCause(t));
    }

    public static JsonRpcError invalidRequestError() {
        return invalidRequestError((Throwable)null);
    }

    public static JsonRpcError invalidRequestError(String message) {
        return invalidRequestError(message, null);
    }

    public static JsonRpcError invalidRequestError(Throwable t) {
        return invalidRequestError("Invalid Request", t);
    }

    public static JsonRpcError invalidRequestError(String message, Throwable t) {
        return new JsonRpcError(RemoteInvalidRequestException.CODE, message, JsonRpcErrors.toCause(t));
    }

    public static JsonRpcError methodNotFoundError() {
        return methodNotFoundError((Throwable)null);
    }

    public static JsonRpcError methodNotFoundError(String message) {
        return methodNotFoundError(message, null);
    }

    public static JsonRpcError methodNotFoundError(Throwable t) {
        return methodNotFoundError("Method not found", t);
    }

    public static JsonRpcError methodNotFoundError(String message, Throwable t) {
        return new JsonRpcError(RemoteMethodNotFoundException.CODE, message, JsonRpcErrors.toCause(t));
    }

    public static JsonRpcError invalidParamsError() {
        return invalidParamsError((Throwable)null);
    }

    public static JsonRpcError invalidParamsError(String message) {
        return invalidParamsError(message, null);
    }

    public static JsonRpcError invalidParamsError(Throwable t) {
        return invalidParamsError("Invalid params", t);
    }

    public static JsonRpcError invalidParamsError(String message, Throwable t) {
        return new JsonRpcError(RemoteInvalidParamsException.CODE, message, JsonRpcErrors.toCause(t));
    }

    public static JsonRpcError internalError() {
        return internalError((Throwable)null);
    }

    public static JsonRpcError internalError(String message) {
        return internalError(message, null);
    }

    public static JsonRpcError internalError(Throwable t) {
        return internalError("Internal error", t);
    }

    public static JsonRpcError internalError(String message, Throwable t) {
        return new JsonRpcError(RemoteInternalException.CODE, message, JsonRpcErrors.toCause(t));
    }

    public static JsonRpcError serverError(int index, String msg, Throwable t) {
        return new JsonRpcError(RemoteServerException.MIN_CODE - index, msg, JsonRpcErrors.toCause(t));
    }

    /**
     *
     * @param throwable
     * @return
     */
    public static JsonRpcErrorCause toCause(Throwable throwable) {
        if (throwable == null) {
            return null;
        }
        LOG.debug("throwable to cause", throwable);
        JsonRpcErrorCause cause = new JsonRpcErrorCause();
        toCause(cause, throwable);
        return cause;
    }

    private static void toCause(JsonRpcErrorCause cause, Throwable throwable) {
        cause.setErrorClass(throwable.getClass().getName());
        cause.setErrorMessage(throwable.getMessage());
        List<JsonRpcErrorCauseStackTrace> stackTraces = new ArrayList<>();
        for (StackTraceElement element : throwable.getStackTrace()) {
            JsonRpcErrorCauseStackTrace stackTrace = new JsonRpcErrorCauseStackTrace();
            stackTrace.setClassName(element.getClassName());
            stackTrace.setMethodName(element.getMethodName());
            stackTrace.setFileName(element.getFileName());
            stackTrace.setLineNumber(element.getLineNumber());
            stackTraces.add(stackTrace);
        }
        cause.setStackTraces(stackTraces);
        if (throwable.getCause() != null) {
            JsonRpcErrorCause subCause = new JsonRpcErrorCause();
            toCause(subCause, throwable.getCause());
        }
    }

    public static Throwable fromCause(JsonRpcErrorCause cause) {
        if (cause == null) {
            return null;
        }
        String message = cause.getErrorClass();
        if (cause.getErrorMessage() != null) {
            message += ": " + cause.getErrorMessage();
        }
        Throwable causeThrowable = null;
        if (cause.getCause() != null) {
            causeThrowable = fromCause(cause.getCause());
        }

        Throwable throwable = new RuntimeException(message, causeThrowable);

        List<JsonRpcErrorCauseStackTrace> list = cause.getStackTraces();
        if (list != null) {
            StackTraceElement[] elements = new StackTraceElement[list.size()];
            for (int i = 0; i < elements.length; i++) {
                JsonRpcErrorCauseStackTrace stackTrace = list.get(i);
                String className = stackTrace.getClassName();
                String methodName = stackTrace.getMethodName();
                String fileName = stackTrace.getFileName();
                int lineNumber = stackTrace.getLineNumber();
                elements[i] = new StackTraceElement(className, methodName, fileName, lineNumber);
            }
            throwable.setStackTrace(elements);
        }

        return throwable;
    }
}
